from PySide2.QtWidgets import (
    QFileDialog,
)
import os
import json
from PySide2.QtCore import Qt
import pandas as pd
from . import message_box as MBox
from . import config as Config
import csv
# import chardet


class Utils:
    # 读取json文件
    @staticmethod
    def read_json(jsonPath=""):
        if not os.path.exists(jsonPath):
            return {}
        with open(jsonPath, "r", encoding="utf-8") as file:
            data = json.load(file)
        return data

    @staticmethod
    def write_json(jsonPath="", data={}):
        os.makedirs(os.path.dirname(jsonPath), exist_ok=True)
        with open(jsonPath, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=4)

    @staticmethod
    def toFloat(value):
        if value is None or value == "":
            return ""
        try:
            return float(value)
        except (ValueError, TypeError):
            return ""

    @staticmethod
    def toInt(value):
        if value is None or value == "":
            return ""
        try:
            return int(value)
        except (ValueError, TypeError):
            return ""

    @staticmethod
    def toIntOrNone(value):
        if value is None or value == "":
            return None
        try:
            return int(value)
        except (ValueError, TypeError):
            return None

    @staticmethod
    def toFloatOrNone(value):
        if value is None or value == "":
            return None
        try:
            return float(value)
        except (ValueError, TypeError):
            return None

    @staticmethod
    def manageLocalTable(table, path):
        data = []
        for i in range(table.rowCount()):
            row_data = []
            for j in range(table.columnCount()):
                item = table.item(i, j)
                if item is not None:
                    row_data.append(item.text())
                else:
                    row_data.append("")  # 空单元格处理
            data.append(row_data)
        json_data = json.dumps(data, indent=4)
        os.makedirs(os.path.dirname(path), exist_ok=True)
        with open(path, "w", encoding="utf-8") as f:
            f.write(json_data)

    @staticmethod
    def findNullItems(matrix):
        nulls = set()
        for index, sublist in enumerate(matrix):
            firstItem = sublist[0]
            if isinstance(firstItem, str):
                if firstItem is None or firstItem.strip() == "":
                    nulls.add(index + 1)
            else:
                if firstItem is None or firstItem == "":
                    nulls.add(index + 1)
        return nulls

    @staticmethod
    def findDuplicateFirstItems(matrix):
        seen = {}
        duplicates = set()
        for index, sublist in enumerate(matrix):
            firstItem = sublist[0]
            if firstItem in seen:
                # 如果子列表的第一项已经出现过，记录其重复的序号
                duplicates.add(seen[firstItem])
                duplicates.add(index + 1)
            else:
                # 记录子列表的第一项及其首次出现的索引
                seen[firstItem] = index + 1

        return duplicates

    # 更新本地锁库
    @staticmethod
    def alertLockingLibrary(diff, library, file):
        libraryDict = Utils.read_json(file)
        addList = diff[0]
        delList = diff[1]
        for addItem in addList:
            if addItem in libraryDict:
                libraryDict[addItem]["count"] += 1
            else:
                # addContent = Utils.getFullOption(addItem, library)
                libraryDict[addItem] = {"content": library[addItem], "count": 1}
        for delItem in delList:
            if delItem in libraryDict:
                if libraryDict[delItem]["count"] > 1:
                    libraryDict[delItem]["count"] -= 1
                else:
                    del libraryDict[delItem]
        return libraryDict

    # 获取选项完整数据
    @staticmethod
    def getFullOption(name, library):
        optionDict = []
        if library:
            for libraryItem in library:
                if libraryItem[0] == name:
                    optionDict = libraryItem
                    continue
        return optionDict

    # 将库名转为set
    @staticmethod
    def getTableLibrarysName(data, column):
        if not data:
            return set()
        unique_names = set()
        if isinstance(data, list):
            for item in data:
                if item[column]:
                    unique_names.add(item[column])
        elif isinstance(data, dict):
            for _, item in data.items():
                if item[column]:
                    unique_names.add(item[column])
        return unique_names

    # 比较新旧set的不同，return [add,del]
    @staticmethod
    def getSetsDifference(set1, set2):
        return [set2 - set1, set1 - set2]

    @staticmethod
    def updateLockingLibrary(dataList, lastSelects, library, file):
        currentLibrary = set()
        if dataList:
            for dataItem in dataList:
                currentLibrary = currentLibrary | Utils.getTableLibrarysName(
                    dataItem[0], dataItem[1]
                )
        diffList = Utils.getSetsDifference(lastSelects, currentLibrary)
        if not diffList[0] and not diffList[1]:
            return False
        unique_library = Utils.alertLockingLibrary(
            diff=diffList, library=library, file=file
        )
        Utils.write_json(file, unique_library)
        return unique_library

    @staticmethod
    def handleTableNoEdit(data, table):
        pass
        # if data:
        #     dictObj = {}
        #     if isinstance(data, list):
        #         dictObj = {str(sublist[0]): "" for sublist in data if sublist}

        #     elif isinstance(data, dict):
        #         dictObj = data
        #     for row in range(table.rowCount()):
        #         item = table.item(row, 0)
        #         if item and item.text() in dictObj:
        #             item.setFlags(item.flags() & ~Qt.ItemIsEditable)
        #             item.setReadyOnly(True)

    # @staticmethod
    # def handleTableNoEditByKey(data, table, key):
    #     if data:
    #         dictObj = {}
    #         if isinstance(data, dict):
    #             dictObj = {str(sublist[0]): sublist for sublist in data.values()}
    #         for row in range(table.rowCount()):
    #             item = table.item(row, 0)
    #             if item and item.text() in dictObj:
    #                 item.setFlags(item.flags() & ~Qt.ItemIsEditable)
    #                 item.setReadyOnly(True)

    @staticmethod
    def tdListToDict(list):
        if list:
            dict = {sublist[0]: sublist for sublist in list}
            return dict
        return {}

    @staticmethod
    def sortDict(data):
        if data:
            return {k: data[k] for k in sorted(data)}
        return {}

    @staticmethod
    def custom_sort_key(s):
        """自定义排序键，避免使用正则表达式"""
        prefix = s.rstrip("0123456789")  # 去掉字符串末尾的数字
        number_str = s[len(prefix) :]  # 提取数字部分
        number = (
            int(number_str) if number_str.isdigit() else float("inf")
        )  # 如果是数字则转换为整数
        return (prefix, number)

    @staticmethod
    def sortDictCustom(data):
        if data:
            return {k: data[k] for k in sorted(data, key=Utils.custom_sort_key)}
        return {}

    @staticmethod
    def ImportFile():
        # 创建QFileDialog对话框实例
        file_dialog = QFileDialog()
        if Config.isImportJson:
            file_dialog.setNameFilter("JSON files (*.json);;CSV files (*.csv)")
        else:
            file_dialog.setNameFilter("CSV files (*.csv)")
        # 设置对话框标题
        file_dialog.setWindowTitle("选择csv文件")
        # 显示对话框并获取用户选择的文件
        if file_dialog.exec_():
            # 用户点击了“打开”或“保存”，获取选中的文件路径
            file_path = file_dialog.selectedFiles()[0]
            return file_path
        return None

    @staticmethod
    def loadCsv(filePath, labList=[], uniqueLabs=[]):
        # with open(filePath, "rb") as f:
        #     result = chardet.detect(f.read(10000))  # 读取前 10KB 检测
        encodings = ["utf-8", "gbk", "utf-16", "gb18030", "big5"]

        for encoding in encodings:
            try:
                lists = []
                df = pd.read_csv(filePath, encoding=encoding)
                df = df.dropna(how="all")  # 删除所有列都是 NaN 的行
                df = df.fillna(value="")  # 替换所有空值为空字符串
                # 指定列过滤重复项 uniqueLabs
                if uniqueLabs:
                    df.drop_duplicates(
                        subset=uniqueLabs, keep="last", inplace=True
                    )  # 保留重复组中的最后一个出现的行
                # 过滤无效列标签
                for name in labList:
                    if name in df.columns:
                        lists.append(df[name].tolist())
                    else:
                        lists.append(["" for _ in range(df.shape[0])])
                data = []
                # 组装数据
                for i in range(df.shape[0]):
                    rowList = []
                    for j in range(len(labList)):
                        rowList.append(lists[j][i])
                    data.append(rowList)
                return data

            except UnicodeDecodeError:
                print(f"CSV失败(编码:{encoding})")
                continue  # 尝试下一种编码
            except Exception as e:
                print(f"加载CSV失败(编码:{encoding}): {str(e)}")
                continue
            
        MBox.warning("导入失败，请使用UTF-8或GBK编码的CSV文件")
        return False

    @staticmethod
    def loadCsvUniquId(filePath, labList=[], uniqueLabs=[]):
        encodings = ["utf-8", "utf-16", "gbk"]
        flag = 0
        for encoding in encodings:
            try:
                lists = []
                df = pd.read_csv(filePath, encoding=encoding)
                df = df.dropna(how="all")  # 删除所有列都是 NaN 的行
                df = df.fillna(value="")  # 替换所有空值为空字符串
                # 指定列过滤重复项 uniqueLabs
                if uniqueLabs:
                    df.drop_duplicates(
                        subset=uniqueLabs, keep="last", inplace=True
                    )  # 保留重复组中的最后一个出现的行
                # 过滤无效列标签
                for name in labList:
                    if name in df.columns:
                        if name == "id":
                            ids = df["id"].tolist()
                            lists.append(
                                Utils.remove_duplicates_preserve_order(ids)
                            )  # 将重复id置为空字符串
                        else:
                            lists.append(df[name].tolist())
                    else:
                        lists.append(["" for _ in range(df.shape[0])])
                data = []
                # 组装数据
                for i in range(df.shape[0]):
                    rowList = []
                    for j in range(len(labList)):
                        rowList.append(lists[j][i])
                    data.append(rowList)
                return data

            except UnicodeDecodeError:
                if flag == 2:
                    MBox.warning(f"导入失败，推荐使用UTF-8或GBK编码的CSV文件进行导入")
                    return False
                print(f"尝试编码 {encoding} 失败")
                flag = flag + 1

            except FileNotFoundError:
                MBox.warning(f"Error: 文件 '{filePath}' 未找到.")
                return False

            except Exception as e:  # 捕获其他所有异常
                MBox.warning("导入失败")
                print(f"发生未知错误: {e}")
                return False
            
    @staticmethod
    def export_to_csv(
        table,
        file_path = None,
        include_headers = True,
        selected_rows = None,
        selected_columns = None,
        encoding = 'utf-8-sig',
        is_material_library = False
    ) -> bool:
        """
        导出表格数据到CSV文件
        
        参数:
            table: 要导出的QTableWidget实例
            file_path: 可选的文件路径，如果为None则弹出文件对话框
            include_headers: 是否包含表头
            selected_rows: 指定导出的行索引列表(如[0, 2, 3])
            selected_columns: 指定导出的列索引列表(如[1, 3])
            encoding: 文件编码格式
        
        返回:
            bool: 是否导出成功
        """
        # 1. 获取文件路径
        if not file_path:
            file_path, _ = QFileDialog.getSaveFileName(
                table,
                "导出CSV文件",
                "data",
                "CSV文件 (*.csv)"
            )
            if not file_path:
                return False
            
            if not file_path.lower().endswith('.csv'):
                file_path += '.csv'
        
        # 2. 准备数据
        data = Utils.get_table_data(table, include_headers, selected_rows, selected_columns, is_material_library)
        
        # 3. 写入文件
        try:
            with open(file_path, 'w', newline='', encoding=encoding) as f:
                writer = csv.writer(f)
                writer.writerows(data)
            MBox.success(f"导出成功！")
            return True
        except Exception as e:
            MBox.warning(f"导出失败: {str(e)}")
            return False
        
    @staticmethod
    def get_table_data(
        table,
        include_headers = False,
        selected_rows = None,
        selected_columns = None,
        is_material_library = False
    ) :
        """
        获取表格数据（不导出文件）
        
        参数:
            同export_to_csv
        
        返回:
            二维列表格式的表格数据
        """
        data = []
        
        if include_headers:
            headers = []
            cols = selected_columns if selected_columns else range(table.columnCount())
            for col in cols:
                header = table.horizontalHeaderItem(col)
                headers.append(header.text() if header else f"Column {col+1}")
            data.append(headers)
        
        rows = selected_rows if selected_rows else range(table.rowCount())
        cols = selected_columns if selected_columns else range(table.columnCount())
        
        
        for row in rows:
            row_data = []
            for col in cols:
                item = table.item(row, col)
                row_data.append(item.text() if item else "")
            data.append(row_data)
        
        # if not is_material_library:
        #     uniqueDict = {}
        #     for row in rows:
        #         row_data = []
        #         for col in cols:
        #             item = table.item(row, col)
        #             value = item.text() if item else ""
        #             if col == 0:
        #                 if value == "" or value is None or value.strip() == "":
        #                     MBox.warning(f"第{str(row+1)}行id不可为空!")
        #                     return 
        #             row_data.append(value)
                    
                    
        #         nameDict[str(rowData[1])] = rowData
                
        #         # 判断是否为空，空返回int
        #         if rowData[0] == "" or rowData[0] is None:
        #             MBox.warning(f"第{str(row+1)}行id不可为空!")
        #             return row + 1
        #         if rowData[1] == "" or rowData[1] is None:
        #             MBox.warning(f"第{str(row+1)}行名称不可为空!")
        #             return row + 1
                # data.append(row_data)
        
        return data

    @staticmethod
    def remove_duplicates_preserve_order(arr):
        seen = set()
        result = []
        for item in arr:
            if item not in seen:
                seen.add(item)
                result.append(item)
            else:
                result.append("")  # 或者 result.append(None)
        return result

    # 去处空值
    @staticmethod
    def remove_empty_data(data):
        if isinstance(data, dict):  # 如果是字典
            d = {}
            for k, v in data.items():
                if v not in (None, "", [], {}) or isinstance(v, (bool, int, float)):
                    resV = Utils.remove_empty_data(v)
                    if resV or isinstance(v, (bool, int, float)):
                        d[k] = resV

            return d
        elif isinstance(data, list):  # 如果是列表
            cleaned_list = []
            for item in data:
                cleaned_item = Utils.remove_empty_data(item)
                if cleaned_item not in (None, "", [], {}):
                    cleaned_list.append(cleaned_item)
            return cleaned_list if cleaned_list else None
        else:
            return data if data not in (None, "", [], {}) else None

    @staticmethod
    def filterDictEmptyProperty(dataDict):
        copyDict = dataDict.copy()
        for k, v in dataDict.items():
            if v in [None, "", [], {}]:
                del copyDict[k]
                continue
            if Utils.is_two_dimensional_array(v):
                if all(not sub_array for sub_array in v):
                    del copyDict[k]
        return copyDict
        # filtered_data = {
        #     k: v for k, v in dataDict.items()
        #     if v not in ["", [], {}] and (not isinstance(v, str) or v.strip()) or isinstance(v, bool)
        # }
        # return filtered_data

    @staticmethod
    def is_two_dimensional_array(obj):
        # 检查是否是列表
        if not isinstance(obj, list):
            return False

        # 检查每个元素是否也是列表
        return all(isinstance(item, list) for item in obj)

    # 过滤列表尾部空值
    @staticmethod
    def filter_dict_list(data):
        retData = data.copy()
        if isinstance(data, dict):
            for k, item in data.items():
                if isinstance(item, list):
                    temp = Utils.filter_trailing_nones_and_empty_strings(item)
                    retData[k] = temp
            return retData
        elif isinstance(data, list):
            # for item in data:
            if isinstance(data, list):
                retData = Utils.filter_trailing_nones_and_empty_strings(retData)
            return retData

  

    @staticmethod
    def filter_trailing_nones_and_empty_strings(array):
        # 如果是二维数组，递归处理每个子数组
        if isinstance(array, list) and all(isinstance(item, list) for item in array):
            arr = [
                Utils.filter_trailing_nones_and_empty_strings(sub_array)
                for sub_array in array
            ]
            return Utils.is_array_empty_or_null(arr)
        # 如果是一维数组，处理数组本身
        elif isinstance(array, list):
            # 从后向前遍历，移除末尾的 None 和空字符串
            while array and (array[-1] is None or array[-1] == ""):
                array.pop()
            return Utils.is_array_empty_or_null(array)
        else:
            return array

    # 判断一个值 如果不是数组 则返回空数组，如果数组长度不到num则用none补全 并返回这个数组
    @staticmethod
    def process_array(value, num):
        # 如果不是列表，返回空数组
        if not isinstance(value, list):
            return []

        # 如果是列表，检查长度并补全
        if len(value) < num:
            value.extend([None] * (num - len(value)))

        return value

    @staticmethod
    def is_array_empty_or_null(array):
        if not array:
            return []
        # 检查数组中的每个元素是否为空或 None
        if all(
            item in [None, "", [], {}]
            or (isinstance(item, (list, dict)) and len(item) == 0)
            for item in array
        ):
            return []
        return array

    def custom_sort(x):
        return (isinstance(x, str), x)  # 元组的第一个元素用于区分类型

    # 排序并转为{"title": item, "value": item}格式
    @staticmethod
    def convertedSortAndFormat(data={}, isCustom=True):
        converted_dict = {}
        for key, value in data.items():
            if isCustom:
                value.sort(key=Utils.custom_sort)
            else:
                value.sort()
            converted_dict[key] = []
            for item in value:
                converted_dict[key].append({"title": item, "value": item})
        return converted_dict
